package com.zerocarbon.framework.web.exception;

import cn.hutool.core.util.StrUtil;
import com.zerocarbon.framework.common.enums.BaseExceptionCode;
import com.zerocarbon.framework.common.result.Result;
import com.zerocarbon.framework.core.exception.BusinessException;
import com.zerocarbon.framework.core.exception.IdempotencyException;
import com.zerocarbon.framework.core.exception.LockException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.nio.file.AccessDeniedException;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 异常通用处理
 * @author wyf
 * @date 2020年3月5日
 */
@Slf4j
public abstract class DefaultGlobalExceptionAdvice {
	
    /**
     * IllegalArgumentException异常处理返回json
     * 返回状态码:400
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({IllegalArgumentException.class})
    public Result<?> badRequestException(IllegalArgumentException e) {
        return defHandler(BaseExceptionCode.BAD_REQUEST_ERROR.getCode(), BaseExceptionCode.BAD_REQUEST_ERROR.getMessage(), e);
    }

    /**
     * AccessDeniedException异常处理返回json
     * 返回状态码:403
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler({AccessDeniedException.class})
    public Result<?> badMethodExpressException(AccessDeniedException e) {
        return defHandler(BaseExceptionCode.UNAUTHORIZED_ERROR.getCode(), BaseExceptionCode.UNAUTHORIZED_ERROR.getMessage(), e);
    }

    /**
     * 返回状态码:405
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public Result<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
    	return defHandler(BaseExceptionCode.METHOD_NOT_ALLOWED_ERROR.getCode(), BaseExceptionCode.METHOD_NOT_ALLOWED_ERROR.getMessage(), e);
    }

    /**
     * 返回状态码:415
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public Result<?> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
    	MediaType contentType = e.getContentType();
        if (contentType != null) {
            StringBuilder msg = new StringBuilder();
            msg.append("请求类型(Content-Type)[").append(contentType.toString()).append("] 与实际接口的请求类型不匹配");
            return Result.result(BaseExceptionCode.MEDIA_TYPE_EX.getCode(), null, msg.toString());
        }
    	return defHandler(BaseExceptionCode.UNSUPPORTED_MEDIA_TYPE_ERROR.getCode(), BaseExceptionCode.UNSUPPORTED_MEDIA_TYPE_ERROR.getMessage(), e);
    }

    /**
     * SQLException sql异常处理
     * 返回状态码:500
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({SQLException.class})
    public Result<?> handleSQLException(SQLException e) {
        return defHandler(BaseExceptionCode.SQL_EX.getCode(), BaseExceptionCode.SQL_EX.getMessage(), e);
    }

    /**
     * SQLException sql异常处理
     * 返回状态码:500
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({SQLIntegrityConstraintViolationException.class})
    public Result<?> handleSQLException(SQLIntegrityConstraintViolationException e) {
        return defHandler(BaseExceptionCode.SQL_EX.getCode(), BaseExceptionCode.SQL_EX.getMessage(), e);
    }
    
    /**
     * sql异常处理
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(PersistenceException.class)
    public Result<?> persistenceException(PersistenceException ex, HttpServletRequest request) {
        log.error("PersistenceException:", ex);
        if (ex.getCause() instanceof BusinessException) {
        	BusinessException cause = (BusinessException) ex.getCause();
            return Result.result(cause.getCode(), null, cause.getMessage());
        }
        return Result.result(BaseExceptionCode.SQL_EX.getCode(), null, BaseExceptionCode.SQL_EX.getMessage());
    }

    /**
     * sql异常处理
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(MyBatisSystemException.class)
    public Result<?> myBatisSystemException(MyBatisSystemException ex, HttpServletRequest request) {
        log.error("MyBatisSystemException:", ex);
        if (ex.getCause() instanceof PersistenceException) {
            return this.persistenceException((PersistenceException) ex.getCause(), request);
        }
        return Result.result(BaseExceptionCode.SQL_EX.getCode(), null, BaseExceptionCode.SQL_EX.getMessage());
    }
    
    /**
     * 事务异常
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(DataIntegrityViolationException.class)
    public Result<?> dataIntegrityViolationException(DataIntegrityViolationException ex) {
        return defHandler(BaseExceptionCode.SQL_EX.getCode(), BaseExceptionCode.SQL_EX.getMessage(), ex);
    }
    
    /**
     * josn解析异常
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result<?> httpMessageNotReadableException(HttpMessageNotReadableException ex) {
        log.error("HttpMessageNotReadableException:", ex);
        String message = ex.getMessage();
        if (StrUtil.containsAny(message, "Could not read document:")) {
            String msg = String.format("无法正确的解析json类型的参数：%s", StrUtil.subBetween(message, "Could not read document:", " at "));
            return Result.result(BaseExceptionCode.PARAM_EX.getCode(), null, msg);
        }
        return Result.result(BaseExceptionCode.PARAM_EX.getCode(), null, BaseExceptionCode.PARAM_EX.getMessage());
    }
    
    /**
     * 参数类型转换错误
     * @param exception
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(HttpMessageConversionException.class)
    public Result<?> parameterTypeException(HttpMessageConversionException exception) {
        log.error("HttpMessageConversionException:", exception);
    	return Result.result(BaseExceptionCode.PARAM_EX.getCode(), null, BaseExceptionCode.PARAM_EX.getMessage());
    }
    
    /**
     * 参数解析验证不通过
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(BindException.class)
    public Result<?> bindException(BindException ex) {
        log.error("BindException:", ex);
        try {
            String msgs = ex.getBindingResult().getFieldError().getDefaultMessage();
            if (StrUtil.isNotEmpty(msgs)) {
                return Result.result(BaseExceptionCode.PARAM_EX.getCode(), null, msgs);
            }
        } catch (Exception ee) {
        }
        StringBuilder msg = new StringBuilder();
        List<FieldError> fieldErrors = ex.getFieldErrors();
        fieldErrors.forEach((oe) ->
                msg.append("参数:[").append(oe.getObjectName())
                        .append(".").append(oe.getField())
                        .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
        );
        return Result.result(BaseExceptionCode.PARAM_EX.getCode(), null, msg.toString());
    }
    
    /**
     * 参数类型不匹配
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result<?> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
        log.error("MethodArgumentTypeMismatchException:", ex);
        MethodArgumentTypeMismatchException eee = (MethodArgumentTypeMismatchException) ex;
        StringBuilder msg = new StringBuilder("参数：[").append(eee.getName())
                .append("]的传入值：[").append(eee.getValue())
                .append("]与预期的字段类型：[").append(eee.getRequiredType().getName()).append("]不匹配");
        return Result.result(BaseExceptionCode.PARAM_EX.getCode(), null, msg.toString());
    }

    /**
     * 无效参数异常
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(IllegalStateException.class)
    public Result<?> illegalStateException(IllegalStateException ex) {
        log.error("IllegalStateException:", ex);
        return Result.result(BaseExceptionCode.ILLEGALA_ARGUMENT_EX.getCode(), null, BaseExceptionCode.ILLEGALA_ARGUMENT_EX.getMessage());
    }

    /**
     * 缺少必要的参数类型
     * @param ex
     * @param request
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result<?> missingServletRequestParameterException(MissingServletRequestParameterException ex, HttpServletRequest request) {
        log.error("MissingServletRequestParameterException:", ex);
        StringBuilder msg = new StringBuilder();
        msg.append("缺少必须的[").append(ex.getParameterType()).append("]类型的参数[").append(ex.getParameterName()).append("]");
        return Result.result(BaseExceptionCode.ILLEGALA_ARGUMENT_EX.getCode(), null, msg.toString());
    }

    /**
     * 空指针异常
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(NullPointerException.class)
    public Result<?> nullPointerException(NullPointerException ex) {
        log.error("NullPointerException:", ex);
        return Result.result(BaseExceptionCode.NULL_POINT_EX.getCode(), null, BaseExceptionCode.NULL_POINT_EX.getMessage());
    }

    /**
     * 文件上传异常
     * @param ex
     * @param request
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(MissingServletRequestPartException.class)
    public Result<?> missingServletRequestPartException(MissingServletRequestPartException ex, HttpServletRequest request) {
        log.error("MissingServletRequestPartException:", ex);
        return Result.result(BaseExceptionCode.REQUIRED_FILE_PARAM_EX.getCode(), null, BaseExceptionCode.REQUIRED_FILE_PARAM_EX.getMessage());
    }

    /**
     * 系统异常
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(ServletException.class)
    public Result<?> servletException(ServletException ex) {
        log.error("ServletException:", ex);
        String msg = "UT010016: Not a multi part request";
        if (msg.equalsIgnoreCase(ex.getMessage())) {
            return Result.result(BaseExceptionCode.REQUIRED_FILE_PARAM_EX.getCode(), null, BaseExceptionCode.REQUIRED_FILE_PARAM_EX.getMessage());
        }
        return Result.result(BaseExceptionCode.SYSTEM_ERROR.getCode(), null, BaseExceptionCode.SYSTEM_ERROR.getMessage());
    }

    /**
     * 文件处理异常
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(MultipartException.class)
    public Result<?> multipartException(MultipartException ex) {
        log.error("MultipartException:", ex);
        return Result.result(BaseExceptionCode.REQUIRED_FILE_PARAM_EX.getCode(), null, BaseExceptionCode.REQUIRED_FILE_PARAM_EX.getMessage());
    }

    /**
     * jsr 规范中的验证异常 （表单方式：校验失败）
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<String> constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
        log.error("ConstraintViolationException:", ex);
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        return Result.result(BaseExceptionCode.VALID_PARAM_EX.getCode(), null, message);
    }

    /**
     * spring 封装的参数验证异常， 在conttoller中没有写result参数时，会进入 （JSON方式：校验失败后）
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
        log.error("MethodArgumentNotValidException:", ex);
        return Result.result(BaseExceptionCode.VALID_PARAM_EX.getCode(), null, ex.getBindingResult().getFieldError().getDefaultMessage());
    }
    
    /**
     * 其他异常 统一处理
     * 返回状态码:500
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception ex) {
    	if (ex.getCause() instanceof BusinessException) {
            return this.businessException((BusinessException) ex.getCause());
        }
        return defHandler(BaseExceptionCode.INTERNAL_SERVER_ERROR.getCode(), BaseExceptionCode.INTERNAL_SERVER_ERROR.getMessage(), ex);
    }
    
    /**
     * BusinessException 业务异常处理
     * @param ex
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public Result<?> businessException(BusinessException ex) {
        return defHandler(ex.getCode(), ex.getMessage(), ex);
    }

    /**
     * IdempotencyException 幂等性异常
     * 返回状态码:200
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(IdempotencyException.class)
    public Result<?> handleException(IdempotencyException e) {
        return defHandler(e.getCode(), e.getMessage(), e);
    }
    
    /**
     * LockException 分布式锁异常
     * 返回状态码:200
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(LockException.class)
    public Result<?> handleException(LockException e) {
        return defHandler(e.getCode(), e.getMessage(), e);
    }

    private Result<?> defHandler(int code, String msg, Exception e) {
        log.error(msg, e);
        return Result.fail(code, msg);
    }
}
